package rpc.bootstrap.springboot;

import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.ServiceLoader;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.env.Environment;
import rpc.bootstrap.conf.BootProperties;
import rpc.bootstrap.spring.BeanPostProcessor;
import rpc.bootstrap.spring.SpringBootstrap;
import rpc.core.Registry;
import rpc.core.RpcApp;
import rpc.core.RpcAppInitBean;
import rpc.core.RpcData;
import rpc.core.plugin.ServicePlugin;
import rpc.core.plugin.impl.DaoServicePlugin;
import rpc.core.plugin.impl.FlowServicePlugin;
import rpc.core.plugin.impl.RedisServicePlugin;
import rpc.core.plugin.impl.RpcRefererServicePlugin;
import rpc.core.plugin.impl.RpcServerServicePlugin;
import rpc.core.plugin.impl.ThreadDumpPlugin;
import rpc.core.plugin.impl.proxy.AsyncFlowService;
import rpc.core.registry.RegistryManager;

/*********
 *
 *
 *@author 197
 *@date 2020/6/18 20:38
 *
 **/

@Configuration
@EnableConfigurationProperties(BootProperties.class)
@ConditionalOnClass({SpringBootstrap.class})
@ConditionalOnProperty(prefix = "rpc", value = "enabled", matchIfMissing = false)
public class AutoConfiguration implements ApplicationListener<ApplicationEvent> {

  private static final Logger logger = LoggerFactory.getLogger(AutoConfiguration.class);

  public AutoConfiguration() {
    if (logger.isDebugEnabled()) {
      logger.debug("init ...");
    }
  }

  @Bean
  @ConditionalOnMissingBean(BeanPostProcessor.class)
  BeanPostProcessor refererBootPostProcessor() {
    return new BeanPostProcessor();
  }


  @Bean
  public Bootstrap bootstrap(BootProperties bootProperties, Environment environment,
      ApplicationContext context) {
    return new Bootstrap(environment, bootProperties, context);
  }


  @Bean
  @ConditionalOnMissingBean(RpcApp.class)
  public RpcApp rpcApp(Environment environment, ApplicationContext context, Bootstrap bootstrap) {
    String appName = environment.getProperty("spring.application.name", "default-name");
    RpcData.setAppName(appName);
    SpringBootstrap instance = SpringBootstrap.getInstance();
    instance.initApplicationContext(context);
    logger.info("new RPC APP init  end");
    return new RpcApp(appName, bootstrap);
  }


  @Override
  public void onApplicationEvent(ApplicationEvent event) {
    RpcApp rpcApp = SpringBootstrap.getInstance().getRpcApp();
    if (event instanceof ContextRefreshedEvent) {
      rpcApp.start();
    }

    if (event instanceof ContextClosedEvent) {
      rpcApp.close();
      logger.info("rpc close");
      rpcApp.stop();
      logger.info("rpc service port stopped");
      rpcApp.shutdown();
      logger.info("rpc executor shutdown");
    }
  }


  @Bean(initMethod = "init", destroyMethod = "close")
  public DaoServicePlugin dbServicePlugin(ApplicationContext context, Environment environment, RpcApp rpcApp) {
    DaoServicePlugin daoServicePlugin = new DaoServicePlugin(context, environment);
//    rpcApp.getBootstrap().addServiceProxy(daoServicePlugin.getFlowServiceProxy());
    return daoServicePlugin;
  }

  @Bean(initMethod = "init", destroyMethod = "close")
  public RedisServicePlugin redisServicePlugin(ApplicationContext context, Environment environment, RpcApp rpcApp) {
    return new RedisServicePlugin(context, environment);
  }

  @Bean(initMethod = "init", destroyMethod = "close")
  public RpcRefererServicePlugin rpcClientServicePlugin(ApplicationContext context, Environment environment, BeanPostProcessor beanPostProcessor, RpcApp rpcApp) {
    return new RpcRefererServicePlugin(context, environment, beanPostProcessor);
  }


  @Bean(initMethod = "init", destroyMethod = "close")
  public FlowServicePlugin flowServicePlugin(ApplicationContext context, Environment environment, RpcApp rpcApp) {
    return new FlowServicePlugin(context, environment);
  }

  @Bean(initMethod = "init", destroyMethod = "close")
  public RpcServerServicePlugin rpcServerServicePlugin(ApplicationContext context, Environment environment, Bootstrap bootstrap, ServicePlugin flowServicePlugin) {
    return new RpcServerServicePlugin(context, environment, bootstrap, flowServicePlugin);
  }

  @Bean(initMethod = "init", destroyMethod = "close")
  public RpcAppInitBean rpcAppInitBean(RpcApp rpcApp, Bootstrap bootstrap, ApplicationContext context, ObjectProvider<List<ServicePlugin>> serviceObjectProvider) {
    SpringBootstrap.getInstance().setRpcApp(rpcApp);

    Objects.requireNonNull(serviceObjectProvider.getIfAvailable()).forEach(servicePlugin -> bootstrap.addServiceProxy(servicePlugin.getFlowServiceProxy()));

    RpcAppInitBean rpcAppInitBean = new RpcAppInitBean(rpcApp, bootstrap);
    bootstrap.postBuild();
    rpcAppInitBean.postBuild();
    return rpcAppInitBean;
  }

  @Bean(initMethod = "init", destroyMethod = "close")
  public AsyncFlowService asyncFlowService(RpcApp rpcApp, Bootstrap bootstrap, ApplicationContext context) {
    return new AsyncFlowService().setBootstrap(bootstrap).setRpcApp(rpcApp).setContext(context);
  }

  @Bean(initMethod = "init", destroyMethod = "close")
  public ThreadDumpPlugin threadDumpPlugin(ApplicationContext context) {
    return new ThreadDumpPlugin(context);
  }


  @Bean(initMethod = "init", destroyMethod = "close")
  public RegistryManager registryManager(Bootstrap bootstrap, BootProperties bootProperties) {
    String type = bootProperties.getRegistry().getType();
    ServiceLoader<Registry> registryServiceLoader = ServiceLoader.load(Registry.class);

    if (StringUtils.isBlank(type)) {
      return null;
    }
    Registry registry = null;
    Iterator<Registry> iterator = registryServiceLoader.iterator();
    for (Iterator<Registry> it = iterator; it.hasNext(); ) {
      registry = it.next();
      if (registry.getClass().getName().toLowerCase().contains(type.toLowerCase())) {
        break;
      }
    }

    if (Objects.nonNull(registry)) {
      return new RegistryManager().setRegistry(registry).setServiceBeanList(bootstrap.getServiceBeanList())
          .setRegistryBeanList(bootstrap.getRegistryBeanList()).setRefererBeanList(bootstrap.getRefererBeanList());
    }
    return null;
  }

}
